package com.demo.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.demo.common.ResultData;
import com.demo.entity.GoodsStockEntity;
import com.demo.service.GoodsStockService;
import com.demo.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * 扣库存
 *
 * @author wangfengchen
 */
@Slf4j
@RestController
@RequestMapping("/stock")
public class StockController {


  @Autowired
  private GoodsStockService goodsStockService;
  @Autowired
  private RedissonClient redissonClient;
  @Autowired
  private RedisUtil redisUtil;


  /**
   * 扣减库存
   * 注意，此处代码是有问题的，可以使用jmeter进行高并发压测
   *
   * @param goodsId
   * @param count
   * @return
   */
  @GetMapping("/deductStock")
  public ResultData deductStock(@RequestParam Long goodsId, @RequestParam Integer count) {
    // 查询商品库存的库存数量
    LambdaQueryWrapper<GoodsStockEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(GoodsStockEntity::getGoodsId, goodsId);
    GoodsStockEntity goodsStock = goodsStockService.getOne(lambdaQueryWrapper);
    if (goodsStock == null) {
      return ResultData.fail("商品未设置库存");
    }
    // 判断库存数量是否足够
    if (goodsStock.getStock() < count) {
      return ResultData.fail("库存不足");
    }
    // 如果库存数量足够则扣减库存
    LambdaUpdateWrapper<GoodsStockEntity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
    lambdaUpdateWrapper.eq(GoodsStockEntity::getGoodsId, goodsId)
        .set(GoodsStockEntity::getStock, goodsStock.getStock() - count);
    goodsStockService.update(lambdaUpdateWrapper);
    return ResultData.success("库存扣减成功");
  }


  /**
   * 扣减库存，优化后
   * <p>
   * 使用 ew.paramNameValuePairs 语法：
   * 这是 MyBatis-Plus 3.5.5 中访问参数的唯一方式
   * ew 代表 Entity Wrapper
   * paramNameValuePairs 是存储参数的 Map
   * 参数命名规则：
   * 必须使用 MPGENVAL + 数字 的格式
   * 推荐顺序递增：MPGENVAL1, MPGENVAL2, MPGENVAL3...
   * 参数设置：
   * 通过 wrapper.getParamNameValuePairs().put("MPGENVAL1", value) 添加参数
   * 参数名必须与 SQL 中的占位符一致
   * <p>
   * 注意：缺点：1、锁范围过大，因为update语句事物没有提交前，会锁住其他更新操作，可以在条件字段上加索引goodsId。
   * 2、无法获取扣减库存之前的库存值。
   *
   * @param goodsId
   * @param count
   * @return
   */
  @GetMapping("/deductStockAndCount")
  public ResultData deductStockAndCount(@RequestParam Long goodsId, @RequestParam Integer count) {
    // 如果库存数量足够则扣减库存
    LambdaUpdateWrapper<GoodsStockEntity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
    lambdaUpdateWrapper.eq(GoodsStockEntity::getGoodsId, goodsId)
        .ge(GoodsStockEntity::getStock, count)
        .setSql("stock = stock - #{ew.paramNameValuePairs.MPGENVAL1}");
    Map<String, Object> paramMap = lambdaUpdateWrapper.getParamNameValuePairs();
    paramMap.put("MPGENVAL1", count);
    boolean update = goodsStockService.update(lambdaUpdateWrapper);
    return ResultData.success(update ? "库存扣减成功" : "库存不足");
  }

  /**
   * 使用 Redisson 优化后
   *
   * @param goodsId
   * @param count
   * @return
   */
  @GetMapping("/deductStockRedisson")
  public ResultData deductStockRedisson(@RequestParam Long goodsId, @RequestParam Integer count) {
    RLock lock = null;
    try {
      lock = redissonClient.getLock("lock" + goodsId);
      lock.lock();
      // 查询商品库存的库存数量
      String stock = redisUtil.get("stock" + goodsId);
      if (StringUtils.isBlank(stock)) {
        return ResultData.fail("商品不存在");
      }
      int surplusStock = Integer.parseInt(stock);
      // 判断库存数量是否足够
      if (surplusStock < count) {
        return ResultData.fail("库存不足");
      }
      // 如果库存数量足够则扣减库存
      redisUtil.set("stock" + goodsId, String.valueOf(surplusStock - count));
      return ResultData.success("库存扣减成功");
    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      if (lock != null) {
        lock.unlock();
      }
    }
  }


}
